# The MIT License
#
# Copyright (c) 2007 Leah Culver
# Copyright (c) 2009 Canonical, Ltd.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
'''
OAuth Client interface
'''
import random
import urllib
import urlparse

from warnings import warn

def escape(s):
    '''Escape a URL including any /.'''
    return urllib.quote(s, safe='~')

def generate_nonce(length=8):
    '''Generate pseudorandom number.'''
    return ''.join([str(random.randint(0, 9)) for i in range(length)])

def _utf8_str(s):
    """Convert unicode to utf-8."""
    if isinstance(s, unicode):
        return s.encode("utf-8")
    else:
        return str(s)

class OAuthRequest(object):
    '''OAuthRequest represents the request and can be serialized.

    OAuth parameters:
        - oauth_consumer_key 
        - oauth_token
        - oauth_signature_method
        - oauth_signature 
        - oauth_timestamp 
        - oauth_nonce
        - oauth_version
        ... any additional parameters, as defined by the Service Provider.
    '''
    def __init__(self, *args, **kwargs):
        self.http_method = kwargs.get('http_method', 'POST')
        self.http_url = kwargs.get('http_url', None)
        self.parameters = kwargs.get('parameters', {})
        self.consumer = kwargs.get('consumer', None)
        self.token = kwargs.get('token', None)
        self.verifier = kwargs.get('verifier', None)
        if not self.verifier and self.token and self.token.verifier:
            self.verifier = self.token.verifier
        self.callback = kwargs.get('callback', None)

        # Set up some parameters
        if self.token:
            self.parameters['oauth_token'] = self.token.key
        if self.consumer:
            self.parameters['oauth_consumer_key'] = self.consumer.key
        if self.verifier:
            self.parameters['oauth_verifier'] = self.verifier
        if self.callback:
            self.parameters['oauth_callback'] = self.callback
        
    def set_parameter(self, parameter, value):
        self.parameters[parameter] = value

    def get_parameter(self, parameter):
        try:
            return self.parameters[parameter]
        except KeyError:
            return None

    # Compat for the HMAC-SHA1 signature method (deprecated)
    def get_normalized_parameters(self):
        """Return a string that contains the parameters that must be signed."""
        params = self.parameters
        try:
            # Exclude the signature if it exists.
            del params['oauth_signature']
        except:
            pass
        # Escape key values before sorting.
        key_values = [(escape(_utf8_str(k)), escape(_utf8_str(v))) \
            for k,v in params.items()]
        # Sort lexicographically, first after key, then after value.
        key_values.sort()
        # Combine key value pairs into a string.
        return '&'.join(['%s=%s' % (k, v) for k, v in key_values])

    def get_normalized_http_method(self):
        """Uppercases the http method."""
        return self.http_method.upper()

    def get_normalized_http_url(self):
        """Parses the URL and rebuilds it to be scheme://host/path."""
        parts = urlparse.urlparse(self.http_url)
        scheme, netloc, path = parts[:3]
        # Exclude default port numbers.
        if scheme == 'http' and netloc[-3:] == ':80':
            netloc = netloc[:-3]
        elif scheme == 'https' and netloc[-4:] == ':443':
            netloc = netloc[:-4]
        return '%s://%s%s' % (scheme, netloc, path)

    # Signing
    def sign_request(self, signature_method, consumer, token):
        """Set the signature parameter to the result of build_signature."""
        # Set the signature method.
        self.set_parameter('oauth_signature_method',
            signature_method.get_name())
        # Set the signature.
        self.set_parameter('oauth_signature',
            signature_method.build_signature(self, consumer, token))

    # Serialize the data
    def to_header(self, realm=''):
        """Serialize as a header for an HTTPAuth request."""
        auth_header = 'OAuth realm="%s"' % realm
        # Add the oauth parameters.
        if self.parameters:
            for k, v in self.parameters.iteritems():
                if k.startswith('oauth_'):
                    auth_header += ', %s="%s"' % (k, escape(str(v)))
        return {'Authorization': auth_header}

    def to_postdata(self):
        """Serialize as post data for a POST request."""
        return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) \
            for k, v in self.parameters.iteritems()])

    def to_url(self):
        """Serialize as a URL for a GET request."""
        return '%s?%s' % (self.http_url, self.to_postdata())

    # Static methods (deprecated)
    @staticmethod
    def from_consumer_and_token(consumer, token=None, callback=None,
                                verifier=None, http_method=None,
                                http_url=None, parameters=None,
                                *args, **kwargs):
        warn("Calling OAuthRequest static methods is deprecated. " \
                 "You should use the default constructor instead.",
             DeprecationWarning)
        newargs = {}
        if token:
            newargs['token'] = token
        if callback:
            newargs['callback'] = callback
        if http_method:
            newargs['http_method'] = http_method
        if http_url:
            newargs['http_url'] = http_url
        if parameters:
            newargs['parameters'] = parameters
        newargs.update(kwargs)
        return OAuthRequest(consumer=consumer, *args, **newargs)

    @staticmethod
    def from_token_and_callback(token, callback=None, http_method=None,
                                http_url=None, parameters=None,
                                *args, **kwargs):
        warn("Calling OAuthRequest static methods is deprecated. " \
                 "You should use the default constructor instead.",
             DeprecationWarning)
        newargs = {}
        if callback:
            newargs['callback'] = callback
        if http_method:
            newargs['http_method'] = http_method
        if http_url:
            newargs['http_url'] = http_url
        if parameters:
            newargs['parameters'] = parameters
        newargs.update(kwargs)

        return OAuthRequest(token=token, *args, **newargs)



class OAuthClient(object):
    '''OAuthClient is a worker to attempt to execute a request.'''

    def __init__(self, oauth_consumer, oauth_token):
        warn("Using %s is deprecated." % self.__name__, DeprecationWarning)
        self.consumer = oauth_consumer
        self.token = oauth_token

    def get_consumer(self):
        return self.consumer

    def get_token(self):
        return self.token
